Bitmap获取缩略图

前言

回顾了下以前写的调用相机和相册的功能,准备把它们整合下,想起曾经用魅族在获取大图时OOM的问题,决定重看一遍当初的解决方式。在获取缩略图步骤上发现了系统已经提供了工具类ThumbnailUtils,当然减少内存消耗不只有这一步。

先前获取缩略图的方法

public static Bitmap getThumbnail(Uri uri,int size, Context context) throws Exception {
    InputStream input = context.getContentResolver().openInputStream(uri);

    //配置BitmapFactory.Options,inJustDecodeBounds设为true,以获取图片的宽高
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither=true;//optional
    onlyBoundsOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional

    //计算inSampleSize缩放比例
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1))
        return null;
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > size) ? (originalSize / size) : 1.0;
    //获取到缩放比例后,再次设置BitmapFactory.Options,获取图片缩略图
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither=true;//optional
    bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();
    return bitmap;
}

/**
 * 将double的比例采用近似值的方式转为int
 * @param ratio
 * @return
 */
private static int getPowerOfTwoForSampleRatio(double ratio){
    int k = Integer.highestOneBit((int)Math.floor(ratio));
    if(k==0) return 1;
    else return k;
}

总体思想是通过设置BitmapFactory.Options.inJustDecodeBounds设为true,先获取到图片的宽高而并不会生产Bitmap;再通过所需图片的最长边size来获取缩放比例inSampleSize的值;
然后获所需尺寸的图片。

获取缩放比例inSampleSize值的算法可以单独拉出一个方法,根据需求进行设置:

/**
 * 根据图片的Options和期望的图片大小来获取图片的缩小比例、
 * 如果图片的宽或高有一个大于目标值,就做处理;否则不做处理。
 * 关于inSampleSize需要注意,它只能是2的次方,否则它会取最接近2的次方的值。
 * @param options   目标图片的BitmapFactory.Options
 * @param expectationWidth 期望图片的宽
 * @param expectationHeight 期望图片的高
 * @return
 */
public static int sampleSize(BitmapFactory.Options options, int expectationWidth, int expectationHeight) {
    //首先获取图片的宽
    int rawWidth = options.outWidth;
    int rawHeight = options.outHeight;
    //在计算图片的sampleSize
    int inSampleSize  = 0;
    if (rawHeight > expectationHeight || rawWidth > expectationWidth) {
        float ratioHeight = rawHeight / expectationHeight;
        float rationWidth = rawWidth / expectationWidth;
        inSampleSize  = (int) Math.min(ratioHeight, rationWidth);
    }
    inSampleSize = Math.max(inSampleSize, 1);
    return inSampleSize ;
}

这种缩放是考虑短的边进行缩放控制,如果短边长度小于期望长度,不进行缩放。

通过上述方法成功获取到图片缩略图,但系统已经给我们提供获取方法,而且算法上考虑的情况更优秀,下面来看看。

ThumbnailUtils

获取图片缩略图,我们使用的方法是extractThumbnail,但主要实现方法是transform.我的是API23的源码。

 /**
 * Transform source Bitmap to targeted width and height.
 */
private static Bitmap transform(Matrix scaler,
        Bitmap source,
        int targetWidth,
        int targetHeight,
        int options) {
    //是否可以进行图片放大操作
    boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
    //是否可以进行原图资源回收操作
    boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;

    int deltaX = source.getWidth() - targetWidth;
    int deltaY = source.getHeight() - targetHeight;
    
    /*
    *图片如果小于目标值,进行放大处理
    */
    if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
        /*
        * In this case the bitmap is smaller, at least in one dimension,
        * than the target.  Transform it by placing as much of the image
        * as possible into the target and leaving the top/bottom or
        * left/right (or both) black.
        */
        Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
        Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b2);

        int deltaXHalf = Math.max(0, deltaX / 2);
        int deltaYHalf = Math.max(0, deltaY / 2);
        Rect src = new Rect(
        deltaXHalf,
        deltaYHalf,
        deltaXHalf + Math.min(targetWidth, source.getWidth()),
        deltaYHalf + Math.min(targetHeight, source.getHeight()));
        int dstX = (targetWidth  - src.width())  / 2;
        int dstY = (targetHeight - src.height()) / 2;
        Rect dst = new Rect(
                dstX,
                dstY,
                targetWidth - dstX,
                targetHeight - dstY);
        c.drawBitmap(source, src, dst, null);
        if (recycle) {
            source.recycle();
        }
        c.setBitmap(null);
        return b2;
    }
    
    /*
    *图片如果大于目标值,进行缩小处理
    */
    float bitmapWidthF = source.getWidth();
    float bitmapHeightF = source.getHeight();

    float bitmapAspect = bitmapWidthF / bitmapHeightF;
    float viewAspect   = (float) targetWidth / targetHeight;
    
    //获取缩放比例,如果原图宽高比大于目标宽高比,也就是原图变得更“窄”了
    //就用高度比例进行缩放,否则用宽度比例进行缩放。
    //效果上看就是将图片完全展示。
    if (bitmapAspect > viewAspect) {
        float scale = targetHeight / bitmapHeightF;
        if (scale < .9F || scale > 1F) {
            scaler.setScale(scale, scale);
        } else {
            scaler = null;
        }
    } else {
        float scale = targetWidth / bitmapWidthF;
        if (scale < .9F || scale > 1F) {
            scaler.setScale(scale, scale);
        } else {
            scaler = null;
        }
    }
    
    //根据缩放比例创建缩略图
    Bitmap b1;
    if (scaler != null) {
        // this is used for minithumb and crop, so we want to filter here.
        b1 = Bitmap.createBitmap(source, 0, 0,
        source.getWidth(), source.getHeight(), scaler, true);
    } else {
        b1 = source;
    }

    if (recycle && b1 != source) {
        source.recycle();
    }

    int dx1 = Math.max(0, b1.getWidth() - targetWidth);
    int dy1 = Math.max(0, b1.getHeight() - targetHeight);

    Bitmap b2 = Bitmap.createBitmap(
            b1,
            dx1 / 2,
            dy1 / 2,
            targetWidth,
            targetHeight);

    if (b2 != b1) {
        if (recycle || b1 != source) {
            b1.recycle();
        }
    }

    return b2;
}

可以看出,系统在效果上看就是将图片完全展示,算法上考虑的情况也更为丰富。原图的回收,是否放大,目标图片的宽高等考虑进去了。

总结

获取缩略图总的步骤就是:


graph TB
A{开始}-->B(获取原始图片宽高)
B --> C[根据算法获取缩放比例]
C --> D[根据缩放比例创建缩略图]


ThumbnailUtils的行数只有521行,还有些方法并有被使用到,感兴趣的可以去看看。
虽然ThumbnailUtils的代码不长,但学到了不同的实现方式和写作方式,了解了自己的不足。多看源码对自己的提升还是很有用的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,513评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,312评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,124评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,529评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,937评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,913评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,084评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,816评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,593评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,788评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,267评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,601评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,265评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,158评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,953评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,066评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,852评论 2 277

推荐阅读更多精彩内容